package org.chinacareer.position.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import javax.annotation.Resource;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.chinacareer.candidate.document.Candidate;
import org.chinacareer.candidate.document.Resume;
import org.chinacareer.candidate.enums.ResumeTypeEnum;
import org.chinacareer.common.document.FieldValue;
import org.chinacareer.common.document.MTemplate;
import org.chinacareer.common.enums.TemplateEnum;
import org.chinacareer.common.pojo.Converter;
import org.chinacareer.common.pojo.TemplateCriteria;
import org.chinacareer.config.AgiPromptConfig;
import org.chinacareer.constant.Constant;
import org.chinacareer.constant.ThreadLocalMapper;
import org.chinacareer.dao.candidate.ICandidateDao;
import org.chinacareer.dao.common.ITemplateDao;
import org.chinacareer.dao.position.IPositionDao;
import org.chinacareer.entity.Account;
import org.chinacareer.mapper.position.AgiFeedBackMapper;
import org.chinacareer.position.api.IAgiService;
import org.chinacareer.position.api.IDataWorkService;
import org.chinacareer.position.api.IPositionService;
import org.chinacareer.position.document.JobSalaryGroup;
import org.chinacareer.position.document.Position;
import org.chinacareer.position.entity.AgiFeedbackEntity;
import org.chinacareer.position.pojo.AgiAdditionalReq;
import org.chinacareer.position.pojo.AgiFeedBackReq;
import org.chinacareer.position.pojo.AgiReq;
import org.chinacareer.position.pojo.AgiResp;
import org.chinacareer.position.pojo.JobSalaryRangeReq;
import org.chinacareer.util.BeanUtil;
import org.chinacareer.util.DateUtil;
import org.chinacareer.util.HttpClientUtil;
import org.chinacareer.util.ObjectUtil;
import org.chinacareer.web.RespCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.TcpClient;

/**
 * @author erek xu
 * @Description:
 * @ClassName:
 * @date 2023/06/13 10:29
 */
@Slf4j
@Service
@RefreshScope
public class AgiServiceImpl implements IAgiService {

    static List<String> agiJdKeys = Lists.newArrayList("positionName", "education", "address", "salaryRange", "describtion", "requirement");
    static List<String> agiJdBaseInfoKeys = Lists.newArrayList("positionName", "titleCategory", "industry", "deptAscription", "address", "positionNumber", "yearOfwork", "education", "salaryRange", "openDay", "endDay", "candidateTag", "describtion", "requirement");
    static List<String> agiCvCategory = Lists.newArrayList("BasicInformation", "WorkExperience", "ProExperience", "EducationExperience", "Skills", "PrizeWinning");
    @Value("${agi.url:http://114.116.1.247/multimodal/}")
    private String agiUrl;
    @Value("${agi.openai-url:https://careerai.openai.azure.com/openai/deployments/career_gpt/chat/completions?api-version=2023-03-15-preview}")
    private String openAiUrl;
    @Value("${agi.openai-url-gpt4:https://careerai.openai.azure.com/openai/deployments/career_gpt/chat/completions?api-version=2023-03-15-preview}")
    private String openAiUrlGPT4;
    @Value("${agi.api-key:d1e0d35e350b4d4884408287466538f4}")
    private String apiKey;
    @Resource
    private IPositionDao positionDao;

    @Resource
    private ICandidateDao candidateDao;

    @Resource
    private IPositionService positionService;

    @Resource
    private ITemplateDao templateDao;

    @Resource
    private AgiPromptConfig agiPromptConfig;

    @Resource
    private AgiFeedBackMapper agiFeedBackMapper;


    @Resource
    private IDataWorkService dataWorkService;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public String cvInterviewQuestion(AgiReq agiReq) {
        return postJdCvAgiRequest(agiUrl + "cv_interview_question", agiReq);
    }

    @Override
    public String cvRecommendReason(AgiReq agiReq) {
        return postJdCvAgiRequest(agiUrl + "cv_recommend_reason", agiReq);
    }
    
    @Override
    public Flux<ServerSentEvent<String>> cvRecommendReasonSse(AgiAdditionalReq agiAdditionalReq) {
        String prompt = getCvRecommendReasonPrompt(agiAdditionalReq);
        Position position = positionDao.findById(agiAdditionalReq.getPositionId());
        Candidate candidate = candidateDao.findCandidateById(agiAdditionalReq.getCandidateId());
        String agiJd = assembleAgiJd(position);
        String agiCv = assembleAgiCv(candidate);
        String cvRecommendReasonContext = assembleCvRecommendReasonContext(prompt, agiJd, agiCv, agiAdditionalReq);
        String endMessage = "";
        if(ObjectUtil.isNotEmpty(position)){
            // 职位薪酬竞争力建议
            JobSalaryGroup jobSalaryGroup = dataWorkService.jobSalaryRange(JobSalaryRangeReq.builder()
                    .jobCategory(position.getTitleCategory().get(0)) // 职位类别
                    .jobCity(position.getAddressId()) // 工作地点
                    .jobDegree(position.getEducation()) // 学历要求
                    .jobWorkExperienceRange(position.getYearOfwork()) // 工作年限
                    .build());

            if(jobSalaryGroup != null && !ObjectUtil.isEmpty(jobSalaryGroup.getJobSalaryRange())){
                // 获取当前职位薪酬
                String salaryRange = position.getPositionInfoList().stream()
                        .filter(fieldValue -> "salaryRange".equals(fieldValue.getKey()))
                        .findFirst()
                        .map(FieldValue::getContents)
                        .orElse("");

                endMessage = "\n\n" +
                        "此外，以下是基于市场公开数据，针对当前职位的薪酬竞争力建议，供参考：\n" +
                        "当前城市同类职位的整体薪酬范围为" + jobSalaryGroup.getJobSalaryRangeOverall()
                        + "/年，其中占比最高为" + jobSalaryGroup.getJobSalaryRange()
                        + "/年，当前职位薪酬为" + salaryRange
                        + "/年";
            }
        }
        
        Flux<String> result = gptSse(cvRecommendReasonContext).map(event -> {
    		String content = "";
    		try {
    			if(!"[DONE]".equals(event)) {
    				JSONObject dataObject = JSONObject.parseObject(event);
    				if (dataObject != null && dataObject.containsKey("choices")) {
    					JSONObject choicesObject = dataObject.getJSONArray("choices").getJSONObject(0);
    					if (choicesObject != null && choicesObject.containsKey("delta")) {
    						JSONObject deltaObject = choicesObject.getJSONObject("delta");
    						if (deltaObject != null && deltaObject.containsKey("content")) {
    							content = deltaObject.getString("content");
    						}
    					}
    				}
    			}
			} catch (Exception e) {
				log.error(">>>>>>>>>>>>>cvRecommendReasonSse error. event:"+event, e);
			}
    		return content;
    	});
//        return result.map(data -> ServerSentEvent.builder(data).build());
    	return result.concatWith(Flux.just(endMessage)).log().onBackpressureBuffer().map(data -> ServerSentEvent.builder(data).build());
    }

    @Override
    public SseEmitter cvRecommendReasonSse2(AgiAdditionalReq agiAdditionalReq) {
        String prompt = getCvRecommendReasonPrompt(agiAdditionalReq);
        Position position = positionDao.findById(agiAdditionalReq.getPositionId());
        Candidate candidate = candidateDao.findCandidateById(agiAdditionalReq.getCandidateId());
        String agiJd = assembleAgiJd(position);
        String agiCv = assembleAgiCv(candidate);
        String cvRecommendReasonContext = assembleCvRecommendReasonContext(prompt, agiJd, agiCv, agiAdditionalReq);
        String endMessage = "";
        if(ObjectUtil.isNotEmpty(position)){
            // 职位薪酬竞争力建议
            JobSalaryGroup jobSalaryGroup = dataWorkService.jobSalaryRange(JobSalaryRangeReq.builder()
                    .jobCategory(position.getTitleCategory().get(0)) // 职位类别
                    .jobCity(position.getAddressId()) // 工作地点
                    .jobDegree(position.getEducation()) // 学历要求
                    .jobWorkExperienceRange(position.getYearOfwork()) // 工作年限
                    .build());

            if(jobSalaryGroup != null && !ObjectUtil.isEmpty(jobSalaryGroup.getJobSalaryRange())){
                // 获取当前职位薪酬
                String salaryRange = position.getPositionInfoList().stream()
                        .filter(fieldValue -> "salaryRange".equals(fieldValue.getKey()))
                        .findFirst()
                        .map(FieldValue::getContents)
                        .orElse("");

                endMessage = "\n\n" +
                        "此外，以下是基于市场公开数据，针对当前职位的薪酬竞争力建议，供参考：\n" +
                        "当前城市同类职位的整体薪酬范围为" + jobSalaryGroup.getJobSalaryRangeOverall()
                        + "/年，其中占比最高为" + jobSalaryGroup.getJobSalaryRange()
                        + "/年，当前职位薪酬为" + salaryRange
                        + "/年";
            }
        }

        SseEmitter emitter = new SseEmitter(600000L);
        String lastMessage = endMessage;
        gptSse(cvRecommendReasonContext).flatMap(s -> Flux.fromArray(s.split("\n")))
                .subscribe(data -> {
                    // 内容解析
                    String content = "";
                    try {
                        if(!"[DONE]".equals(data)) {
                            JSONObject dataObject = JSONObject.parseObject(data);
                            if (dataObject != null && dataObject.containsKey("choices")) {
                                JSONObject choicesObject = dataObject.getJSONArray("choices").getJSONObject(0);
                                if (choicesObject != null && choicesObject.containsKey("delta")) {
                                    JSONObject deltaObject = choicesObject.getJSONObject("delta");
                                    if (deltaObject != null && deltaObject.containsKey("content")) {
                                        content = deltaObject.getString("content");
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        log.error(">>>>>>>>>>>>>cvRecommendReasonSse error. event:"+data, e);
                    }

                    try {
                        // 返回内容
                        emitter.send(content);
                    } catch (Exception e) {
                        log.error("[Sender] 客户端SSE发送错误", e);
                    }
                }, e -> {
                    log.error("[Sender] 客户端SSE连接错误", e);
                }, () -> {
                    try {
                        // 职位薪酬竞争力建议
                        emitter.send(lastMessage);
                        emitter.complete();
                    } catch (Exception e) {
                        log.error("[Sender] 客户端SSE发送错误", e);
                        emitter.completeWithError(e);
                    }
                });

        return emitter;
    }

    /**
     * 获取gpt4用户列表
     *
     * @param 
     * @return
     */
    @Override
    public Set<String> gpt4AccountList() {
    	Set<String> accountSet = Sets.newHashSet();
    	accountSet = redisTemplate.opsForSet().members(Constant.OPENAIURL_GPT4_ACCOUNT);
    	return accountSet;
    }
    /**
     * 候选人推荐理由优化
     *
     * @param agiAdditionalReq
     * @return
     */
    @Override
    public String cvRecommendReasonOptimization(AgiAdditionalReq agiAdditionalReq) {
        String prompt = getCvRecommendReasonPrompt(agiAdditionalReq);
        Position position = positionDao.findById(agiAdditionalReq.getPositionId());
        Candidate candidate = candidateDao.findCandidateById(agiAdditionalReq.getCandidateId());
        String agiJd = assembleAgiJd(position);
        String agiCv = assembleAgiCv(candidate);
        String cvRecommendReasonContext = assembleCvRecommendReasonContext(prompt, agiJd, agiCv, agiAdditionalReq);
        String result = gptResp(cvRecommendReasonContext);
        if(ObjectUtil.isNotEmpty(position)){
            // 职位薪酬竞争力建议
            JobSalaryGroup jobSalaryGroup = dataWorkService.jobSalaryRange(JobSalaryRangeReq.builder()
                    .jobCategory(position.getTitleCategory().get(0)) // 职位类别
                    .jobCity(position.getAddressId()) // 工作地点
                    .jobDegree(position.getEducation()) // 学历要求
                    .jobWorkExperienceRange(position.getYearOfwork()) // 工作年限
                    .build());

            if(jobSalaryGroup != null && !ObjectUtil.isEmpty(jobSalaryGroup.getJobSalaryRange())){
                // 获取当前职位薪酬
                String salaryRange = position.getPositionInfoList().stream()
                        .filter(fieldValue -> "salaryRange".equals(fieldValue.getKey()))
                        .findFirst()
                        .map(FieldValue::getContents)
                        .orElse("");

                return result + "\n\n" +
                        "此外，以下是基于市场公开数据，针对当前职位的薪酬竞争力建议，供参考：\n" +
                        "当前城市同类职位的整体薪酬范围为" + jobSalaryGroup.getJobSalaryRangeOverall()
                        + "/年，其中占比最高为" + jobSalaryGroup.getJobSalaryRange()
                        + "/年，当前职位薪酬为" + salaryRange
                        + "/年";
            }
        }
        return result;
    }

    @Override
    public String cvOptimization(AgiReq agiReq) {
        return postJdCvAgiRequest(agiUrl + "cv_optimization", agiReq);
    }

    @Override
    public String cvCommunicationQuestion(AgiReq agiReq) {
        return postJdCvAgiRequest(agiUrl + "cv_communication_question", agiReq);
    }

    /**
     * 根据职位信息提供目标寻访公司、人选title和行业背景建议
     *
     * @param positionId
     * @return
     */
    @Override
    public String provideCompanyAndCandidateTitleWithJd(String positionId) {
        Position position = positionDao.findById(positionId);
        if (ObjectUtil.isEmpty(position)) {
            return "";
        }
        String prompt = agiPromptConfig.getJdProvideCompanyAndCvTitle();
        String content = prompt + "\n"
                + "职位JD信息：\"\"\"" + "\n"
                + "客户名称: " + position.getCustomerName() + "\n"
                + assembleJdBaseInfo(position)
                + "\"\"\"";
        return gptResp(content);
    }

    /**
     * 保存agi意见反馈信息
     *
     * @param agiFeedBackReq
     */
    @Override
    public void saveAgiFeedBackInfo(AgiFeedBackReq agiFeedBackReq) {
        AgiFeedbackEntity agiFeedbackEntity = new AgiFeedbackEntity();
        BeanUtil.copyProperties(agiFeedBackReq, agiFeedbackEntity);
        Account account = ThreadLocalMapper.getAccount();
        agiFeedbackEntity.setSubmitCiId(account.getUserName());
        agiFeedbackEntity.setSubmitName(account.getEnglishName() + "(" + account.getName() + ")");
        agiFeedbackEntity.setSubmitTime(DateUtil.format(new Date(), DateUtil.FORMAT_DATE_YMDHMS));
        agiFeedBackMapper.insert(agiFeedbackEntity);
    }

    private WebClient getWebClient() {
        TcpClient tcpClient = TcpClient
                .create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60 * 1000)
                .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(120 * 1000, TimeUnit.MILLISECONDS)));

        return WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
                .build();
    }

    /**
     * 原生AGI接入，流式输出
     *
     * @param content
     * @return
     */
    @Override
    public Flux<String> gptSse(String content) {
        WebClient webClient = getWebClient();
        Map<String, Object> map = Maps.newHashMap();
        map.put("stream", true);
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map1 = Maps.newHashMap();
        map1.put("role", "user");
        map1.put("content", content);
        list.add(map1);
        map.put("messages", list);
        map.put("temperature", 0.5);
//        Set<String> accountSet = redisTemplate.opsForSet().members(Constant.OPENAIURL_GPT4_ACCOUNT);
//        String url = (CollectionUtils.isNotEmpty(accountSet) && accountSet.contains(ThreadLocalMapper.getAccount().getUid()))?openAiUrlGPT4:openAiUrl;
        String url = openAiUrlGPT4;
        return webClient.post()
                .uri(url)
                .header("api-key", apiKey)
                .syncBody(map)
                .exchange()
                .flatMapMany(clientResponse -> clientResponse.bodyToFlux(String.class))
                .publishOn(Schedulers.elastic());
    }

//    public String gptResp(String content) {
//        WebClient webClient = getWebClient();
//        Map<String, Object> map = Maps.newHashMap();
//        List<Map<String, Object>> list = new ArrayList<>();
//        Map<String, Object> map1 = Maps.newHashMap();
//        map1.put("role", "user");
//        map1.put("content", content);
//        list.add(map1);
//        map.put("messages", list);
//        Mono<String> body = webClient.post()
//                .uri(openAiUrl)
//                .header("api-key", apiKey)
//                .body(Mono.just(map), Map.class)
//                .retrieve().bodyToMono(String.class);
//        JSONObject jsonObject = JSONObject.parseObject(body.block());
//        String choices = jsonObject.getString("choices");
//        List<String> lists = JSONObject.parseArray(choices, String.class);
//        return JSONObject.parseObject(JSONObject.parseObject(lists.get(0)).getString("message")).getString("content");
//    }

    @Override
    public String gptResp(String content) {
        Map<String, Object> map = Maps.newHashMap();
        Map<String, Object> header = Maps.newHashMap();
        header.put("api-key", apiKey);
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> mapMessage = Maps.newHashMap();
        mapMessage.put("role", "user");
        mapMessage.put("content", content);
        list.add(mapMessage);
        map.put("messages", list);
        map.put("temperature", 0.5);
        Set<String> accountSet = redisTemplate.opsForSet().members(Constant.OPENAIURL_GPT4_ACCOUNT);
        String url = (CollectionUtils.isNotEmpty(accountSet) && accountSet.contains(ThreadLocalMapper.getAccount().getUid()))?openAiUrlGPT4:openAiUrl;
        return postAgiRequest(url, header, map);
    }

    /**
     * 获取推荐理由提示词
     *
     * @param agiAdditionalReq
     * @return
     */
    private String getCvRecommendReasonPrompt(AgiAdditionalReq agiAdditionalReq) {
        String prompt;
        switch (agiAdditionalReq.getOperType()) {
            case 1:
                if (StringUtils.isBlank(agiAdditionalReq.getPositionId())) {
                    prompt = agiPromptConfig.getCvRecommandReasonNoJd();
                } else {
                    prompt = agiPromptConfig.getCvRecommandReasonWithJd();
                }
                break;
            case 2:
                prompt = agiPromptConfig.getCvRecommandReasonMakeItLonger();
                break;
            default:
                prompt = agiPromptConfig.getCvRecommandReasonNoJd();
                break;
        }
        return prompt;
    }

    /**
     * 组装简历推荐理由上下文
     *
     * @param prompt
     * @param agiJd
     * @param agiCv
     * @param agiAdditionalReq
     * @return
     */
    private String assembleCvRecommendReasonContext(String prompt, String agiJd, String agiCv, AgiAdditionalReq agiAdditionalReq) {
        String context = prompt + "\n"
                + "简历信息：\n"
                + "'''\n"
                + agiCv
                + "'''\n"
                + "职位信息：\n"
                + "'''\n"
                + agiJd
                + "'''\n";
        if (agiAdditionalReq.getOperType() == 2) {
            context += "\n"
                    + "该人选简历的简要推荐建议：\n"
                    + "'''\n"
                    + agiAdditionalReq.getCvRecommandReason() + "\n"
                    + "'''\n";
        }
        return context;
    }

    private String postJdCvAgiRequest(String url, AgiReq agiReq) {
        Position position = positionDao.findById(agiReq.getPositionId());
        Candidate candidate = candidateDao.findCandidateById(agiReq.getCandidateId());
        String agiJd = assembleAgiJd(position);
        String agiCv = assembleAgiCv(candidate);
        if (ObjectUtil.isEmpty(url)) {
            return null;
        }
        Map<String, Object> param = Maps.newHashMap();
        param.put("jd_txt", agiJd);
        param.put("cv_txt", agiCv);
        return postAgiRequest(url, param);
    }

    private String postAgiRequest(String url, Map<String, Object> param) {
        String resp = HttpClientUtil.httpPostRequest(url, JSONObject.toJSONString(param), 60 * 1000, 20 * 1000);
        try {
            AgiResp agiResp = JSONObject.parseObject(resp, AgiResp.class);
            if (RespCode.OK == agiResp.getCode()) {
                return agiResp.getResult();
            }
            throw new RuntimeException("状态码异常:" + resp);
        } catch (Exception e) {
            log.error("agi内部接口调用异常, 入参: " + JSONObject.toJSONString(param) + "返回参: " + resp, e);
            return null;
        }
    }

    private String postAgiRequest(String url, Map<String, Object> header, Map<String, Object> param) {
        String resp = HttpClientUtil.httpPostRequestForAgi(url, header, JSONObject.toJSONString(param), 120 * 1000, 20 * 1000);
        try {
            JSONObject jsonObject = JSONObject.parseObject(resp);
            String choices = jsonObject.getString("choices");
            List<String> lists = JSONObject.parseArray(choices, String.class);
            return JSONObject.parseObject(JSONObject.parseObject(lists.get(0)).getString("message")).getString("content");
        } catch (Exception e) {
            log.error("agi原生接口调用异常, 入参: " + JSONObject.toJSONString(param) + "返回参: " + resp, e);
            throw new RuntimeException("agi原生接口调用异常:" + resp);
        }
    }

    private String assembleAgiJd(Position position) {
        if (ObjectUtil.isEmpty(position)) {
            return "";
        }
        Converter.FieldValueToTemplate(position, positionService.getTemplate());
        List<FieldValue> positionInfoList = position.getPositionInfoList();
        if (CollectionUtils.isNotEmpty(positionInfoList)) {
            StringBuilder agiJd = new StringBuilder();
            positionInfoList.forEach(fieldValue -> {
                if (agiJdKeys.contains(fieldValue.getKey())) {
                    agiJd.append(buildValue(fieldValue));
                }
            });
            return agiJd.toString();
        }
        return "";
    }

    /**
     * 组装职位基本信息
     *
     * @param position
     * @return
     */
    private String assembleJdBaseInfo(Position position) {
        if (ObjectUtil.isEmpty(position)) {
            return "";
        }
        Converter.FieldValueToTemplate(position, positionService.getTemplate());
        List<FieldValue> positionInfoList = position.getPositionInfoList();
        if (CollectionUtils.isNotEmpty(positionInfoList)) {
            StringBuilder agiJd = new StringBuilder();
            positionInfoList.forEach(fieldValue -> {
                if (agiJdBaseInfoKeys.contains(fieldValue.getKey())) {
                    agiJd.append(buildValue(fieldValue));
                }
            });
            return agiJd.toString();
        }
        return "";
    }

    private String assembleAgiCv(Candidate candidate) {
        if (ObjectUtil.isEmpty(candidate)) {
            return "";
        }
        AtomicInteger resumeType = new AtomicInteger(ResumeTypeEnum.CN.getValue());
        List<FieldValue> fieldValues = Optional.of(candidate)
                .map(c -> {
                    Resume resume = c.getCnResume();
                    if (ObjectUtil.isEmpty(resume)) {
                        resume = c.getEnResume();
                        resumeType.set(ResumeTypeEnum.EN.getValue());
                    }
                    return resume;
                })
                .map(Resume::getValues)
                .orElseGet(Lists::newArrayList);
        if (CollectionUtils.isNotEmpty(fieldValues)) {
            Map<String, List<FieldValue>> category = Maps.newLinkedHashMap();
            fieldValues.forEach(fieldValue -> {
                if (agiCvCategory.contains(fieldValue.getCategoryKey())) {
                    List<FieldValue> fvs = category.computeIfAbsent(fieldValue.getCategoryKey(), v -> Lists.newArrayList());
                    fvs.add(fieldValue);
                }
            });
            StringBuilder agiCv = new StringBuilder();
            MTemplate mTemplate = queryTemplate(TemplateEnum.CANDIDATE.getValue(), resumeType.get());
            category.forEach((k, v) -> {
                agiCv.append(buildCategory(mTemplate, k));
                v.forEach(fieldValue -> agiCv.append(buildValue(fieldValue)));
            });
            return agiCv.toString();
        }
        return "";
    }

    private String buildValue(FieldValue fieldValue) {
        return fieldValue.getFieldName() + ": " + (fieldValue.getContents() != null ? fieldValue.getContents() : fieldValue.getValue()) + "\n";
    }

    private String buildCategory(MTemplate mTemplate, String categoryKey) {
        AtomicReference<String> category = new AtomicReference<>();
        Optional.ofNullable(mTemplate)
                .map(MTemplate::getCategoryList)
                .ifPresent(fieldCategories -> fieldCategories.forEach(fieldCategory -> {
                    if (categoryKey.equals(fieldCategory.getKey())) {
                        category.set(fieldCategory.getName() + ": \n");
                    }
                }));
        return category.toString();
    }

    private MTemplate queryTemplate(Integer templateType, Integer languageType) {
        TemplateCriteria criteria = new TemplateCriteria();
        criteria.setTemplateType(templateType);
        criteria.setLanguageType(languageType);
        return templateDao.queryTemplateByCriteria(criteria);
    }

}
